#ifndef XENENGIN_XEN_RENDERER_GENERAL_MATERIAL_HPP
#define XENENGIN_XEN_RENDERER_GENERAL_MATERIAL_HPP

#include "texture.hpp"
#include "shader.hpp"
#include "buffer.hpp"

namespace Xen {

struct ShaderUniformData {
ShaderDataType type;
std::variant<float, glm::vec2, glm::vec3, glm::vec4,
             int, glm::ivec2, glm::ivec3, glm::ivec4,
             glm::mat3, glm::mat4> data;

template <typename T>
static ShaderUniformData Create(const T& value);
};

template <>
inline ShaderUniformData ShaderUniformData::Create<float>(const float& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Float;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::vec2>(const glm::vec2& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Float2;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::vec3>(const glm::vec3& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Float3;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::vec4>(const glm::vec4& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Float4;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::mat3>(const glm::mat3& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Mat3;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::mat4>(const glm::mat4& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Mat4;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<int>(const int& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Int;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::ivec2>(const glm::ivec2& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Int2;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::ivec3>(const glm::ivec3& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Int3;
    data.data = value;
    return data;
}

template <>
inline ShaderUniformData ShaderUniformData::Create<glm::ivec4>(const glm::ivec4& value) {
    ShaderUniformData data;
    data.type = ShaderDataType::Int4;
    data.data = value;
    return data;
}

class Material {
public:
    static Ref<Material> Create(const Ref<Shader>& shader);
    Material(const Ref<Shader>& shader);

    void SetData(const std::string& name, const ShaderUniformData& prop);
    void SetTexture(const std::string& name, const Ref<Texture2D>& texture);

    ShaderUniformData GetData(const std::string& name) const;
    Ref<Texture2D> GetTexture(const std::string& name) const;

    uint32_t GetDataNum() const { return datas_.size(); }
    uint32_t GetTextureNum() const { return textures_.size(); }

    Ref<Shader> GetShader() { return shader_; }
    void Use();

private:
    Ref<Shader> shader_;
    std::unordered_map<std::string, ShaderUniformData> datas_;
    std::unordered_map<std::string, Ref<Texture2D>> textures_;

    void uniformShaderData(const std::string& name, const ShaderUniformData& data);
};

}

#endif
